package baseFx.common.lock.redis;

import baseFx.common.Assert;
import baseFx.common.AutoCloseable;
import baseFx.common.concurrent.Task;
import baseFx.common.utils.FxDate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.Random;
import java.util.UUID;

class SimpleLocker implements AutoCloseable {
    public static final int HeartbeatSeconds = 5;
    private String uuid = UUID.randomUUID().toString();
    private JedisPool pool;
    private String lockKey;
    private Heartbeat heartbeat;

    public SimpleLocker(JedisPool pool) {
        this.pool = pool;
    }

    public boolean lock(String key) {
        Assert.isNull(lockKey);
        String k = key + "_RSLOCK";
        try (Jedis j = pool.getResource()) {
            String rs = j.set(k, uuid, SetParams.setParams().nx().ex(HeartbeatSeconds));
            if ("ok".equalsIgnoreCase(rs)) {
                lockKey = k;
                heartbeat = new Heartbeat();
                return true;
            }
            return false;
        }
    }

    @Override
    public void close() {
        if (lockKey != null) {
            heartbeat.cancel();
            heartbeat = null;
            try (Jedis j = pool.getResource()) {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                j.eval(script, Collections.singletonList(lockKey), Collections.singletonList(uuid));
            }
            lockKey = null;
        }
        pool = null;
    }

    private class Heartbeat implements Runnable {
        private String key = lockKey;
        private final Object sync = new Object();

        public Heartbeat() {
            Task.run(this::run);
        }

        @Override
        public void run() {
            Random rd = new Random(System.nanoTime());
            long avg = HeartbeatSeconds * FxDate.SECONDS / 5;
            while (key != null) {
                Task.delay(avg * 3 + rd.nextInt((int) avg));
                synchronized (sync) {
                    if (key == null) {
                        break;
                    }
                    try (Jedis j = pool.getResource()) {
                        String s = j.get(key);
                        if (uuid.equalsIgnoreCase(s)) {
                            Long t = j.expire(key, HeartbeatSeconds);
                            if (t == 0) {
                                key = null;
                            }
                        } else {
                            key = null;
                        }
                    }
                }
            }
        }

        public void cancel() {
            synchronized (sync) {
                key = null;
            }
        }
    }
}
